<?php
namespace app\console\server;

use app\swoole\model\ScheduleTask;

class TcpServer {

    protected $setting = [
        'reactor_num' => 1, //reactor线程数,一般设置为CPU核数的1-4倍
        'worker_num' => 2, //worker进程数量，一般设置为CPU核数的1-4倍
        'max_request' => 1000, //worker进程处理多少任务数之后，自动销毁
        'task_worker_num' => 10, //task进程的数量
        'task_ipc_mode' => 3, //task和worker进程的通信方式
        'task_max_request' => 200, //task进程处理多少任务数之后退出
        'daemonize' => 1,
        'log_file' => RUNTIME_PATH . 'swoole/swoole.log'
    ];
    public static $master_pid_file = RUNTIME_PATH . 'swoole/master.pid';

    public function __construct($setting = []) {
        $this->setting = array_merge($this->setting, $setting);
        if (!file_exists(RUNTIME_PATH . 'swoole/')) {
            mkdir(RUNTIME_PATH . 'swoole/', 0777, TRUE);
        }
    }

    public function run() {
        $swoole_server = new \Swoole\Server('0.0.0.0', 9501);
        $swoole_server->set($this->setting);
        $swoole_server->on('receive', [$this, 'receive']);
        $swoole_server->on('workerstart', [$this, 'workerstart']);
        $swoole_server->on('task', [$this, 'taskhandle']);
        $swoole_server->on('finish', [$this, 'finish']);
        $swoole_server->on('workererror', [$this, 'workererror']);
        $swoole_server->on('start', [$this, 'start']);
        $swoole_server->start();
    }

    public function receive(\Swoole\Server $swoole_server, $fd, $from_id, $data) {
        
    }

    /**
     * worker进程、task进程启动的回调函数
     * @param \Swoole\Server $swoole_server
     * @param type $worker_id
     */
    public function workerstart(\Swoole\Server $swoole_server, $worker_id) {
        if (!$swoole_server->taskworker && $worker_id == 0) {  //在最后一个worker进程启动后注册定时任务
            $swoole_server->after((60 - date('s')) * 1000, function() use($swoole_server) { //整点执行
                $swoole_server->tick(60000, function ($tick_id) use ($swoole_server) {
                    $next_minute_timestamp = strtotime('+1 minute');
                    $task_list = ScheduleTask::needRegister($next_minute_timestamp);
                    $model_taskrunlog = new \app\swoole\model\TaskRunlog();
                    foreach ($task_list as $task) {
                        $task_data = $model_taskrunlog->addLog($task);
                        $time_ms = floor((strtotime($task_data['plan_start_time']) - microtime(TRUE)) * 1000);
                        $swoole_server->after($time_ms, function() use ($swoole_server, $task_data) {
                            $swoole_server->task(json_encode($task_data));
                        });
                    }
                });
            });
        }
    }

    /**
     * 处理task_work进程投递过来的任务
     * @param \Swoole\Server $swoole_server
     * @param int $task_id   
     * @param int $from_id    来自于那个task_worker进程
     * @param string $data    投递的任务的内容
     */
    public function taskhandle(\Swoole\Server $swoole_server, $task_id, $from_id, $data) {
        // 更新任务开始执行的时间,和执行状态
        $task_data = json_decode($data, true);
        \app\swoole\model\TaskRunlog::update(['real_start_time' => date('Y-m-d H:i:s'), 'status' => 2], ['id' => $task_data['log_id']]);
        // 异常处理程序
        switch ($task_data['type']) {
            case 'think_command':   // thinkphp的Command执行方式
                $command_path = ROOT_PATH . 'think ' . $task_data['cmd_path'];
                $task_data['result'] = exec('php ' . $command_path);
                break;
            case 'callback':
                $task_data['result'] = call_user_func([$task_data['cmd_path'], $task_data['cmd_action']]);
                break;
            default:
                break;
        }
        $swoole_server->finish(json_encode($task_data));
    }

    /**
     * task处理完成之后的回调，更新task的状态
     * @param \Swoole\Server $swoole_server
     * @param type $task_id
     * @param type $data
     */
    public function finish(\Swoole\Server $swoole_server, $task_id, $data) {
        $task_result = json_decode($data, true);
        $upcloumn = ['run_end_time' => date('Y-m-d H:i:s'), 'status' => 3, 'error_log' => isset($task_result['result']) ? $task_result['result'] : ''];
        \app\swoole\model\TaskRunlog::update($upcloumn, ['id' => $task_result['log_id']]);
    }

    /**
     * 记录worker进程的错误信息
     * @param \Swoole\Server $swoole_server
     * @param type $worker_id
     * @param type $worker_pid
     * @param type $exit_code
     */
    public function workererror(\Swoole\Server $swoole_server, $worker_id, $worker_pid, $exit_code) {
        $error_str = 'work_id:' . $worker_id . ' worker_pid:' . $worker_pid . ' error_code:' . $exit_code . PHP_EOL;
        file_put_contents(RUNTIME_PATH . 'swoole/worker_error.log', $error_str, FILE_APPEND);
    }

    public function start(\Swoole\Server $swoole_server) {
        file_put_contents(self::$master_pid_file, $swoole_server->master_pid);
        cli_set_process_title('Main Process');
    }

    static public function stop() {
        $pid = file_get_contents(self::$master_pid_file);
        exec('kill -15 ' . $pid);
        unlink(self::$master_pid_file);
    }

    static public function reload() {
        $pid = file_get_contents(self::$master_pid_file);
        exec('kill -10 ' . $pid);
    }

}
